﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;

namespace Behaviorize.TextEditor.Outlining
{
	internal sealed class OutliningTagger : ITagger<IOutliningRegionTag>
	{
		private readonly ITextBuffer _buffer;
		private ITextSnapshot _snapshot;
		private List<Region> _regions;

		public OutliningTagger(ITextBuffer buffer)
		{
			_buffer = buffer;
			_snapshot = buffer.CurrentSnapshot;
			_regions = new List<Region>();
			Reparse();
			_buffer.Changed += BufferChanged;
		}

		private void Reparse()
		{
			ITextSnapshot newSnapshot = _buffer.CurrentSnapshot;
			List<Region> newRegions = new List<Region>();
			SnapshotPoint? featureStart = null, scenarioStart = null;

			foreach (var line in newSnapshot.Lines)
			{
				string text = line.GetText();
				bool eof = line.LineNumber == newSnapshot.LineCount - 1;

				if (text.Trim().ToLowerInvariant().StartsWith("feature:") || eof)
				{
					if (featureStart != null)
					{
						CreateFeatureRegion(newRegions, ref featureStart, eof, line, newSnapshot);

						if (scenarioStart != null)
						{
							CreateScenarioRegion(newRegions, ref scenarioStart, eof, line, newSnapshot);
						}
					}
					else
					{
						featureStart = line.Start;
					}
				}

				if (text.Trim().ToLowerInvariant().StartsWith("scenario:") || eof)
				{
					if (scenarioStart != null)
					{
						CreateScenarioRegion(newRegions, ref scenarioStart, eof, line, newSnapshot);
					}
					else
					{
						scenarioStart = line.Start;
					}
				}
			}

			_snapshot = newSnapshot;
			_regions = newRegions;

			if (TagsChanged != null)
			{
				TagsChanged(this, new SnapshotSpanEventArgs(new SnapshotSpan(_snapshot, 0, _snapshot.Length)));
			}
		}

		private static void CreateFeatureRegion(List<Region> newRegions, ref SnapshotPoint? featureStart, bool eof, ITextSnapshotLine line, ITextSnapshot newSnapshot)
		{
			var actualLine = newSnapshot.GetLineFromLineNumber(
				eof ? line.LineNumber : line.LineNumber - 1);

			while (string.IsNullOrWhiteSpace(actualLine.GetText()))
			{
				actualLine = newSnapshot.GetLineFromLineNumber(actualLine.LineNumber - 1);
			}

			newRegions.Add(new Region()
				{
					Start = featureStart.Value,
					End = newSnapshot.GetLineFromLineNumber(actualLine.LineNumber).End
				});

			featureStart = !eof ? line.Start : (SnapshotPoint?)null;
		}

		private static void CreateScenarioRegion(List<Region> newRegions, ref SnapshotPoint? scenarioStart, bool eof, ITextSnapshotLine line, ITextSnapshot newSnapshot)
		{
			var actualLine = newSnapshot.GetLineFromLineNumber(
				eof ? line.LineNumber : line.LineNumber - 1);

			while (string.IsNullOrWhiteSpace(actualLine.GetText()))
			{
				actualLine = newSnapshot.GetLineFromLineNumber(actualLine.LineNumber - 1);
			}

			newRegions.Add(new Region
				{
					Start = scenarioStart.Value,
					End = newSnapshot.GetLineFromLineNumber(actualLine.LineNumber).End
				});

			scenarioStart = !eof ? line.Start : (SnapshotPoint?)null;
		}

		void BufferChanged(object sender, TextContentChangedEventArgs e)
		{
			// If this isn't the most up-to-date version of the buffer, then ignore it for now (we'll eventually get another change event).
			if (e.After != _buffer.CurrentSnapshot)
				return;
			
			Reparse();
		}

		public IEnumerable<ITagSpan<IOutliningRegionTag>> GetTags(NormalizedSnapshotSpanCollection spans)
		{
			return spans.Count == 0 ? Enumerable.Empty<ITagSpan<IOutliningRegionTag>>() : GetTags((IEnumerable<SnapshotSpan>)spans);
		}

		public IEnumerable<ITagSpan<IOutliningRegionTag>> GetTags(IEnumerable<SnapshotSpan> snapshotSpans)
		{
			List<SnapshotSpan> spans = snapshotSpans.ToList();
			List<Region> currentRegions = _regions;
			ITextSnapshot currentSnapshot = _snapshot;
			SnapshotSpan entire = new SnapshotSpan(spans[0].Start, spans[spans.Count - 1].End).TranslateTo(currentSnapshot, SpanTrackingMode.EdgeExclusive);
			int startLineNumber = entire.Start.GetContainingLine().LineNumber;
			int endLineNumber = entire.End.GetContainingLine().LineNumber;

			foreach (var region in currentRegions)
			{
				ITextSnapshotLine regionEndLineNumber = currentSnapshot.GetLineFromPosition(region.End.Position);
				ITextSnapshotLine regionStartLineNumber = currentSnapshot.GetLineFromPosition(region.Start.Position);

				if (regionEndLineNumber.LineNumber <= endLineNumber &&
					regionStartLineNumber.LineNumber >= startLineNumber)
				{
					yield return new TagSpan<IOutliningRegionTag>(
						region.ToSnapshotSpan(),
						new OutliningRegionTag(false, false, "...", "mouseover"));
				}
			}
		}

		public event EventHandler<SnapshotSpanEventArgs> TagsChanged;

		class PartialRegion
		{
			public SnapshotPoint Start { get; set; }
		}

		class Region : PartialRegion
		{
			public SnapshotPoint End { get; set; }

			public SnapshotSpan ToSnapshotSpan()
			{
				return new SnapshotSpan(Start, End);
			}
		} 
	}
}